package cn.zf233.xcloud.service.impl;

import cn.zf233.xcloud.commom.Const;
import cn.zf233.xcloud.commom.R;
import cn.zf233.xcloud.commom.enums.DataUnitENUM;
import cn.zf233.xcloud.commom.enums.UserRoleENUM;
import cn.zf233.xcloud.commom.enums.UserSourceENUM;
import cn.zf233.xcloud.commom.enums.UserStatusENUM;
import cn.zf233.xcloud.entity.File;
import cn.zf233.xcloud.vo.IpInfo;
import cn.zf233.xcloud.entity.User;
import cn.zf233.xcloud.mapper.FileMapper;
import cn.zf233.xcloud.mapper.UserMapper;
import cn.zf233.xcloud.service.AdminService;
import cn.zf233.xcloud.service.FileService;
import cn.zf233.xcloud.service.MessageService;
import cn.zf233.xcloud.util.CommonUtil;
import cn.zf233.xcloud.util.RedisUtil;
import cn.zf233.xcloud.util.ThreadSafeDateUtil;
import cn.zf233.xcloud.vo.AdminVo;
import cn.zf233.xcloud.vo.UserVo;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Created by zf233 on 2021/1/17
 * 管理员服务
 */
@Service
public class AdminServiceImpl implements AdminService {

    private final WeekFlowChartEntity weekFlowChartEntity = new WeekFlowChartEntity();
    public static final String GET_REAL_IP_INFO_PREFIX = "";

    @Autowired
    private RestTemplate restTemplate;

    @Resource
    private UserMapper userMapper;

    @Resource
    private FileMapper fileMapper;

    @Resource
    private FileService fileService;

    @Resource
    private MessageService messageService;

    @Resource
    private RedisUtil redisUtil;

    @Override
    public R getAllUserInfo(Integer role) {
        List<User> userList;
        if (UserSourceENUM.exists(role) != null) {
            userList = userMapper.selectUsersBySource(role);
        } else {
            userList = userMapper.selectUsers();
        }
        List<AdminVo> adminVoList = userList
                .stream()
                .map(CommonUtil::assembleAdminVoDetail)
                .peek(user -> {
                    user.setLoginFlag(redisUtil.bitGet(Const.USER_LOGIN_FLAG_BIT_SET_KEY, user.getId()) ? 1 : 0);
                    boolean flag = redisUtil.exists(Const.USER_SESSION_STATUS_PREFIX + user.getId())
                            && redisUtil.ttl(Const.USER_SESSION_STATUS_PREFIX + user.getId()) > 0;
                    user.setSessionStatus(flag ? 1 : 0);
                })
                .collect(Collectors.toList());
        return R.createBySuccess("用户列表获取成功", adminVoList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R lockOrUnlock(User user, Integer lock) {
        if (!(UserStatusENUM.NORMAL_USER.getCode().equals(lock)
                || UserStatusENUM.LOCK_USER.getCode().equals(lock))) {
            return R.createByErrorIllegalArgument("参数有误");
        }
        User userInfo = userMapper.selectByPrimaryKey(user.getId());
        if (userInfo == null) {
            return R.createByErrorMessage("用户不存在");
        }
        if (UserStatusENUM.EMAIL_UNVERIFIED.getCode().equals(userInfo.getStatus())
                || UserStatusENUM.PHONE_UNVERIFIED.getCode().equals(userInfo.getStatus())) {
            return R.createByErrorMessage("当前用户状态不允许操作");
        }
        if (UserRoleENUM.ADMIN.getCode().equals(userInfo.getRole())) {
            return R.createByErrorMessage("无权限");
        }
        if (userInfo.getStatus().equals(lock)) {
            return R.createByErrorMessage("请勿重复操作");
        }
        String msg = UserStatusENUM.LOCK_USER.getCode().equals(lock) ? "锁定" : "解除锁定";
        userInfo.setStatus(lock);
        int result = userMapper.updateByPrimaryKeySelective(userInfo);
        if (result > 0) {
            return R.createBySuccessMessage("用户" + msg + "成功");
        }
        return R.createByErrorMessage("用户" + msg + "失败");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public R removeUser(User user) {
        User userInfo = userMapper.selectByPrimaryKey(user.getId());
        if (UserRoleENUM.ADMIN.getCode().equals(userInfo.getRole())) {
            return R.createByErrorMessage("无权限");
        }
        File rootNode = fileMapper.selectRootNodeOfUserByPrimaryKey(userInfo.getId());
        R response = fileService.remove(new Long[]{rootNode.getId()}, userInfo);
        if (response.isSuccess()) {
            int result = userMapper.deleteByPrimaryKey(userInfo.getId());
            if (result <= 0) {
                return R.createBySuccessMessage("移除失败");
            }
            if (redisUtil.exists(Const.USER_LOGIN_CODE_PHONE_PREFIX + userInfo.getId().toString())) {
                redisUtil.remove(Const.USER_LOGIN_CODE_PHONE_PREFIX + userInfo.getId().toString());
            }
            if (redisUtil.exists(Const.USER_LOGIN_CODE_EMAIL_PREFIX + userInfo.getId().toString())) {
                redisUtil.remove(Const.USER_LOGIN_CODE_EMAIL_PREFIX + userInfo.getId().toString());
            }
            if (StringUtils.isNotBlank(userInfo.getEmail())) {
                messageService.sendEmail(userInfo,
                        "XCloud 用户提醒",
                        new StrBuilder("因使用违规，您的账号")
                                .append(CommonUtil.getDisplayName(userInfo))
                                .append("已经被XCloud自动移除，XCloud致力于保护您的隐私，您的满意是我们前进的动力。")
                                .toString(),
                        null,
                        false);
            }
            return R.createBySuccessMessage("移除成功");
        }
        return response;
    }

    @Override
    public R userTypeChart() {
        List<User> userList = userMapper.selectUsers();
        long qqUserCount = userList.stream().filter(user
                -> UserSourceENUM.QQ_USER.getCode().equals(user.getSource())).count();
        long emailUserCount = userList.stream().filter(user
                -> UserSourceENUM.EMAIL_USER.getCode().equals(user.getSource())).count();
        long phoneUserCount = userList.stream().filter(user
                -> UserSourceENUM.PHONE_USER.getCode().equals(user.getSource())).count();
        Map<String, Long> map = new HashMap<>(4);
        map.put(UserSourceENUM.QQ_USER.getKey(), qqUserCount);
        map.put(UserSourceENUM.EMAIL_USER.getKey(), emailUserCount);
        map.put(UserSourceENUM.PHONE_USER.getKey(), phoneUserCount);
        return R.createBySuccess("用户来源分布图表参数获取成功", map);
    }

    @Override
    public R<Map<String, Object>> flowChart() {
        try {
            return R.createBySuccess("月流量出入图表参数获取成功", weekFlowChartEntity.get());
        } catch (RuntimeException e) {
            return R.createByErrorMessage("月流量出入图表参数获取失败");
        }
    }

    @Override
    public void uploadFlow(Long flow) {
        weekFlowChartEntity.addUploadFlow(flow);
    }

    @Override
    public void downloadFlow(Long flow) {
        weekFlowChartEntity.addDownloadFlow(flow);
    }

    @Override
    public void flushWeekFlow() {
        weekFlowChartEntity.flush();
    }

    public void setIpInfo(UserVo userVo) {
        try {
            boolean flag = redisUtil.exists(Const.USER_SESSION_STATUS_PREFIX + userVo.getId())
                    && redisUtil.ttl(Const.USER_SESSION_STATUS_PREFIX + userVo.getId()) > 0;
            if (!flag) {
                return;
            }
            String ip = redisUtil.get(Const.USER_SESSION_STATUS_PREFIX + userVo.getId());
            if (StringUtils.isBlank(ip)) {
                return;
            }
            ResponseEntity<IpInfo> entity = restTemplate.getForEntity(GET_REAL_IP_INFO_PREFIX + ip, IpInfo.class);
            if (entity.getStatusCode().is2xxSuccessful() && entity.getBody() != null && entity.getBody().success()) {
                userVo.setIpInfo(entity.getBody());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class FlowEntity {

        private final Long timestamp;
        private String day;
        private Long uploadFlow;
        private Long downloadFlow;

        public FlowEntity(Long timestamp, Long uploadFlow, Long downloadFlow) {
            this.timestamp = timestamp;
            this.uploadFlow = uploadFlow;
            this.downloadFlow = downloadFlow;
        }

        public void addUploadFlow(Long flow) {
            this.uploadFlow += flow;
        }

        public void addDownloadFlow(Long flow) {
            this.downloadFlow += flow;
        }

        public void setDay(String day) {
            this.day = day;
        }

        public Long getTimestamp() {
            return timestamp;
        }

        public String getDay() {
            return day;
        }

        public Long getUploadFlow() {
            return uploadFlow;
        }

        public Long getDownloadFlow() {
            return downloadFlow;
        }
    }

    static class WeekFlowChartEntity {

        private final int day = Const.XCLOUD_ADMIN_FLOW_COUNT_DAY;
        private final List<FlowEntity> flowEntityList = new ArrayList<>(day);
        private final SimpleDateFormat format = new SimpleDateFormat("MM.dd");
        private static final String LIST_DAY = "dayList";
        private static final String LIST_FLOW_UP = "uploadFlowList";
        private static final String LIST_FLOW_DOWN = "downloadFlowList";

        {
            // 不存储，启动时生成流量随机的前30天记录
            for (int i = 0; i < day; i++) {
                long l = ThreadSafeDateUtil.now() - TimeUnit.DAYS.toMillis(day - 1 - i);
                flowEntityList.add(new FlowEntity(l, 0L, 0L));
            }
        }

        public Map<String, Object> get() {
            Map<String, Object> map = new HashMap<>();
            List<String> dayList = new ArrayList<>(day);
            List<Long> uploadFlowList = new ArrayList<>(day);
            List<Long> downloadFlowList = new ArrayList<>(day);
            for (FlowEntity flowEntity : flowEntityList) {
                flowEntity.setDay(format.format(flowEntity.getTimestamp()));
                dayList.add(flowEntity.getDay());
                uploadFlowList.add(DataUnitENUM.BYTE.convertUnit(flowEntity.getUploadFlow(), DataUnitENUM.MB));
                downloadFlowList.add(DataUnitENUM.BYTE.convertUnit(flowEntity.getDownloadFlow(), DataUnitENUM.MB));
            }
            map.put(LIST_DAY, dayList);
            map.put(LIST_FLOW_UP, uploadFlowList);
            map.put(LIST_FLOW_DOWN, downloadFlowList);
            return map;
        }

        public void addUploadFlow(Long flow) {
            FlowEntity flowEntity = flowEntityList.get(flowEntityList.size() - 1);
            flowEntity.addUploadFlow(flow);
        }

        public void addDownloadFlow(Long flow) {
            FlowEntity flowEntity = flowEntityList.get(flowEntityList.size() - 1);
            flowEntity.addDownloadFlow(flow);
        }

        public void flush() {
            if (flowEntityList.size() >= day) {
                for (int i = 0; i < flowEntityList.size() - 1; i++) {
                    flowEntityList.set(i, flowEntityList.get(i + 1));
                }
            }
            flowEntityList.set(day - 1, new FlowEntity(ThreadSafeDateUtil.now(), 0L, 0L));
        }
    }
}